// thread 多线程下载器

package internal

import (
	"bufio"
	"context"
	"fmt"
	"io"
	"log"
	"os"
	"strconv"
	"sync"
	"time"
)

const (
	GoroutineNum = 3
	TempSuffix   = ".download"
)

type Thread struct {
	d           *Download
	tempFileDir string
}

func NewThread(d *Download, tempFileDir string) *Thread {
	return &Thread{
		d:           d,
		tempFileDir: tempFileDir,
	}
}

func (t *Thread) Run() {
	if err := CreatePath(t.tempFileDir); err != nil {
		log.Fatal(err)
	}

	// 将文件进行分片下载到临时文件
	// 创建下载writer
	writers := make([]*Writer, GoroutineNum)

	// 每个分片长度
	writeSliceLength := int64(float64(t.d.aim.fileSize) / GoroutineNum)
	// 平均后剩下的长度，放入最后一个分片
	writeLastSliceNum := t.d.aim.fileSize % GoroutineNum
	for i := 0; i < GoroutineNum; i++ {
		var (
			startPos int64
			endPos   int64
		)

		// goroutine1		  | goroutine2		  			  | goroutine3
		// {0}:{28441878 - 1} | {28441878}:{28441878 x 2 - 1} | {28441878 x 2}:{28441878 x 3 - 1 + 2}
		startPos = writeSliceLength * int64(i)

		if i+1 == GoroutineNum {
			endPos = writeSliceLength*int64(i+1) - 1 + writeLastSliceNum
		} else {
			endPos = writeSliceLength*int64(i+1) - 1
		}

		writerNum := make(chan int64)
		fileName := t.tempFileDir + "/" + strconv.Itoa(i+1) + "-" + t.d.aim.fileNameOnly + TempSuffix

		writers[i] = NewWriter(startPos, endPos, fileName, writerNum, make(chan struct{}), t.d.aim)

		go writers[i].store()
	}

	success := make(chan struct{})
	// 输出进度条
	go t.log(writers, success)

	// 阻塞
	<-success

	// 拼接临时文件 && 删除临时文件
	t.splicingFiles(writers)
}

func (t *Thread) log(writers []*Writer, success chan struct{}) {
	defer func() {
		success <- struct{}{}
	}()

	// 循环输出打印信息
	var wg sync.WaitGroup
	wg.Add(len(writers))

	ctx, cancel := context.WithCancel(context.Background())
	dLog := NewDownloadLog(ctx, t.d.aim.fileSize, time.Second)

	for _, writer := range writers {
		go func(w *Writer) {
			for {
				select {
				case <-w.quitSignal:
					wg.Done()
					//log.Println(fmt.Sprintf("%s 完成 ==> 开始位置：%d 结束位置：%d <==", w.fileName, w.startPos, w.endPos))
					return
				case num := <-w.writerNum:
					dLog.AddSchedule(num)
					//log.Println(fmt.Sprintf("%s 已下载数量：%d", w.fileName, num))
				}
			}
		}(writer)
	}

	wg.Wait()
	cancel()

	return
}

func (t *Thread) splicingFiles(writers []*Writer) {
	start := time.Now()
	defer func() {
		log.Println(fmt.Sprintf("合并文件成功，共花费时间: %.2f s", time.Now().Sub(start).Seconds()))
	}()

	// 创建文件下载文件路径
	f, err := os.Create(t.d.downloadFilePath + "/" + t.d.aim.fileName)
	if err != nil {
		log.Fatal(err)
	}
	writer := bufio.NewWriter(f)

	defer func() {
		writer.Flush()
		f.Close()
	}()

	for _, item := range writers {
		fi, err := os.Open(item.fileName)
		if err != nil {
			log.Fatal(err)
		}

		reader := bufio.NewReader(fi)
		buff := make([]byte, 1024)
		for {
			rl, err := reader.Read(buff)
			if err != nil {
				if err == io.EOF {
					break
				} else {
					log.Fatal(err)
				}
			}
			writer.Write(buff[:rl])
		}
		fi.Close()

		// 删除文件
		os.Remove(item.fileName)
	}

	return
}
